import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 连接的工具类，只负责一些公用方法，常用主要方法，不负责维护连接的状态信息。
 * 关于连接池：
 * 只有一个数据源，一个数据源只能用一个用户名和密码。
 * 需要使用非配置文件中的用户和密码新建一个连接时，请使用单独的GetConnection(props,user,password),openDB(user,password)
 * 在本类中，主配置文件的连接一般获取未openDB(),getConnection(),这种无参数的，如果是有参数，那么会返回一个新的连接，这一点
 * 对于连接池的连接尤其关键，因为不同的配置需要设置不同的连接池。
 */
public class Conn_Util {

    private static String mainConfigFilePath="jdbcconfig.properties";
    public final static String driver = "org.postgresql.Driver";
    private static Map<String, DataSource> DATASOURCES = new HashMap<>();  //存放非配置文件中的DataSource,key是用户名
    private static int maxPoolConnections=10;

    public static void setMainConfigFilePath(String jdbcConfigPath){
        Conn_Util.mainConfigFilePath=jdbcConfigPath;
    }
    public static void setMaxPoolConnections(int num){
        Conn_Util.maxPoolConnections=num;
    }
    public static Properties getConfigFromFile(String filePath) {
        Properties props = new Properties();
        try {
            BufferedInputStream config = new BufferedInputStream(new FileInputStream(filePath));
            props.load(config);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return props;
    }

    public static String genURLFromPro(Properties props) {
        String hostname = props.getProperty("hostname");
        String[] hostnames = hostname.split(",");
        String port = props.getProperty("port");
        String[] ports = port.split(",");
        String dbname = props.getProperty("dbname");
        return genURLFromHostsPortsDBname(hostnames, ports, dbname);
    }

    /**
     * 灵活配置选项，比如在配置中配置了多个hostname,一般获取url直接从hostname获取，那么在Map指定
     * hostname到hostname1,就会从hostname1中获取ip
     * 用法：将一个配置值写入map的key，此key对应的值为另一个配置项。
     * 一般用不到，因为可以直接再用例里面写死配置，也就非常极端的情况下，必须灵活配置才会用到。比如主备的ip
     *
     * @param props
     * @param custom
     * @return
     */
    public static Properties genProertiesFromMap(Properties props, Map<String, String> custom) {
        if (custom.isEmpty()) {
            return props;
        }
        for (String key : custom.keySet()) {
            props.put(key, props.get(custom.get(key)));
        }
        return props;
    }

    public static String genURLFromHostsPortsDBname(String[] hostnames, String[] ports, String dbname) {
        String sourceURL;
        if (hostnames.length == 1) {
            sourceURL = "jdbc:postgresql://" + hostnames[0] + ":" + ports[0] + "/" + dbname;
        } else {
            ArrayList<String> ip_port_pare = new ArrayList<>();
            if (ports.length == 1) {
                for (String ip : hostnames) {
                    ip_port_pare.add(ip + ":" + ports[0]);
                }
            } else {
                for (int i = 0; i < hostnames.length; i++) {
                    ip_port_pare.add(hostnames[i] + ":" + ports[i]);
                }
            }
            sourceURL = "jdbc:postgresql://" + String.join(",", ip_port_pare) + "/" + dbname;
        }
        return sourceURL;
    }

    public static Properties getConfigByDefault() {
        return getConfigFromFile(mainConfigFilePath);
    }

    public static String getMainConfigbyName(String ConfigName) {
        Properties p = getConfigByDefault();
        return p.getProperty(ConfigName);
    }

    public static void setDatasource(Properties props, String DatesourceName) {
        if (!Conn_Util.DATASOURCES.containsKey(DatesourceName)) {
            String sourceURL = genURLFromPro(props);
            HikariConfig config = new HikariConfig();
            config.setDataSourceProperties(props);
            config.setJdbcUrl(sourceURL);
            config.setMaximumPoolSize(maxPoolConnections);
            Conn_Util.DATASOURCES.put(DatesourceName, new HikariDataSource(config));
        }
    }

    public static DataSource getDatasource(String DatesourceName) {
        return Conn_Util.DATASOURCES.get(DatesourceName);
    }

    private static Connection GetConnection(Properties props, String DatesourceName) throws SQLException {
        Connection connR;
        String sourceURL = genURLFromPro(props);
        try {
            Class.forName(driver).newInstance();
        } catch (Exception var10) {
            var10.printStackTrace();
            return null;
        }
        if (props.getOrDefault("useConnectionPools", "false").equals("true")) {
            if (!Conn_Util.DATASOURCES.containsKey(DatesourceName)) {
                Conn_Util.setDatasource(props, DatesourceName);
            }
            connR = DATASOURCES.get(DatesourceName).getConnection();
        } else {
            connR = DriverManager.getConnection(sourceURL, props);
        }
        return connR;

    }

    /**
     * 打开一个连接，这个是主配置文件的。连接，如果是连接池，那么就返回主配置文件的连接池
     *
     * @return
     */
    public static Connection openDB() throws SQLException {
        return GetConnection(getConfigByDefault(), "default_connnection");
    }

    /**
     * 返回一个没有额外参数的连接，也是没有连接池的连接，这个很多情况下会用到，独立出来
     *
     * @return
     */
    public static Connection openDB(String url, String user, String password) throws SQLException {
        try {
            Class.forName(driver).newInstance();
        } catch (Exception var10) {
            var10.printStackTrace();
            return null;
        }
        Connection connR = DriverManager.getConnection(url, user, password);
        return connR;
    }

    /**
     * 返回一个没有连接池的连接，这个很多情况下会用到，独立出来
     *
     * @return
     */
    public static Connection openDB(Properties props) throws SQLException {
        try {
            Class.forName(driver).newInstance();
        } catch (Exception var10) {
            var10.printStackTrace();
            return null;
        }
        Connection connR = DriverManager.getConnection(genURLFromPro(props), props);
        return connR;
    }

    /**
     * 使用默认配置连接，但是替换用户名和密码，一样有连接池，会新建一个Datasource,名称即为其用户名
     * 如果不使用连接池，那么返回一个新的连接。
     *
     * @param user
     * @param password
     * @return
     * @throws SQLException
     */
    public static Connection openDB(String user, String password) throws SQLException {
        Properties props = getConfigFromFile(mainConfigFilePath);
        props.put("user", user);
        props.put("password", password);
        return GetConnection(props, user);
    }

    /**
     * 返回一个指定名称的datasource
     * 非连接池模式下，就返回一个新连接
     *
     * @param props
     * @param DatesourceName
     * @return
     * @throws SQLException
     */
    public static Connection openDB(Properties props, String DatesourceName) {
        Connection conn=null;
        try {
            conn = Conn_Util.GetConnection(props, DatesourceName);
        }catch (SQLException sql){
            sql.printStackTrace();
        }
        return conn;
    }

    public static void closeDB(Connection conn) throws SQLException {
        if (conn != null) {
            conn.close();
        }

    }
}
